#include <linux/dev/serial_core.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/init.h>

#include "hal-uart.h"

struct uart8250_port
{
    struct uart_port port;

    struct hal_uart *regbase;
};

static struct uart8250_port *_uart_from_port(struct uart_port *port)
{
    return container_of(port, struct uart8250_port, port);
}

static int _uart_startup(struct uart_port *port)
{
    struct uart8250_port *uart = _uart_from_port(port);

    hal_uart_rxie_set(uart->regbase, true);

    return 0;
}

static void _uart_start_tx(struct uart_port *port)
{
    struct uart8250_port *uart = _uart_from_port(port);
    uint8_t c;

    uart_fifo_get(port, &c);

    hal_uart_putc(uart->regbase, c);
    hal_uart_txie_complete_setup(uart->regbase, true);
}

static void _uart_stop_tx(struct uart_port *port)
{
    struct uart8250_port *uart = _uart_from_port(port);

    hal_uart_txie_complete_setup(uart->regbase, false);
}

static irqreturn_t uart8250_irq(int irq, void *dev_id)
{
    struct uart8250_port *uart = (struct uart8250_port *)dev_id;
    struct uart_port *port = &uart->port;
    uint8_t ch;

    if (hal_uart_getc(uart->regbase, &ch))
    {
        uart_received_char(port, ch);
    }

    if (hal_uart_status_tx_complete(uart->regbase))
    {
        uint8_t c;

        if (uart_fifo_get(port, &c))
        {
            hal_uart_putc(uart->regbase, c);
        }
        else
        {
            uart_tx_stop(port);
        }

        uart_notify_writeable(port);
    }

    return IRQ_HANDLED;
}

static struct uart8250_port _uart_ports[1];
static struct uart_driver _uart_driver = {
    .nr = 1,
};

static const struct uart_ops _uart_ops = {
    .startup = _uart_startup,
    .start_tx = _uart_start_tx,
    .stop_tx = _uart_stop_tx,
};

int uart8250_init(void)
{
    int ret;

    struct uart8250_port *uart = &_uart_ports[0];

    uart->regbase = (struct hal_uart *)0x10000000;

    uart_register_driver(&_uart_driver);
    uart->port.ops = &_uart_ops;

    uart_add_one_port(&_uart_driver, &uart->port);

    ret = request_irq(10, uart8250_irq, 0, "", uart);

    return ret;
}
device_initcall(uart8250_init);
